/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.peer.controller;

import static org.ourgrid.common.interfaces.Constants.LINE_SEPARATOR;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.ourgrid.common.config.Configuration;
import org.ourgrid.common.interfaces.DiscoveryService;
import org.ourgrid.common.interfaces.DiscoveryServiceClient;
import org.ourgrid.common.interfaces.PeerAccounting;
import org.ourgrid.common.interfaces.control.PeerControlClient;
import org.ourgrid.common.interfaces.management.PeerManager;
import org.ourgrid.common.interfaces.management.WorkerManagement;
import org.ourgrid.common.interfaces.status.CommunityInfo;
import org.ourgrid.common.interfaces.status.ConsumerInfo;
import org.ourgrid.common.interfaces.status.LocalConsumerInfo;
import org.ourgrid.common.interfaces.status.NetworkOfFavorsStatus;
import org.ourgrid.common.interfaces.status.PeerStatusProviderClient;
import org.ourgrid.common.interfaces.status.RemoteWorkerInfo;
import org.ourgrid.common.interfaces.to.DiscoveryServiceState;
import org.ourgrid.common.interfaces.to.LocalWorkerState;
import org.ourgrid.common.interfaces.to.TrustyCommunity;
import org.ourgrid.common.interfaces.to.UserInfo;
import org.ourgrid.common.interfaces.to.UserState;
import org.ourgrid.common.interfaces.to.WorkerInfo;
import org.ourgrid.common.spec.worker.WorkerSpec;
import org.ourgrid.common.statistics.beans.peer.Worker;
import org.ourgrid.common.statistics.control.AccountingControl;
import org.ourgrid.common.statistics.control.LoginControl;
import org.ourgrid.common.statistics.control.PeerControl;
import org.ourgrid.common.statistics.control.UserControl;
import org.ourgrid.common.statistics.control.WorkerControl;
import org.ourgrid.common.statistics.util.hibernate.HibernateUtil;
import org.ourgrid.peer.PeerConfiguration;
import org.ourgrid.peer.PeerConstants;
import org.ourgrid.peer.controller.accounting.AccountingConstants;
import org.ourgrid.peer.controller.accounting.AccountingController;
import org.ourgrid.peer.controller.actions.DiscoveryServiceUpdateAction;
import org.ourgrid.peer.controller.actions.RequestWorkersAction;
import org.ourgrid.peer.controller.actions.SaveAccountingAction;
import org.ourgrid.peer.controller.actions.UpdatePeerUptimeAction;
import org.ourgrid.peer.controller.ds.DiscoveryServiceClientController;
import org.ourgrid.peer.controller.ds.DiscoveryServiceNotificationController;
import org.ourgrid.peer.controller.messages.PeerControlMessages;
import org.ourgrid.peer.dao.AccountingDAO;
import org.ourgrid.peer.dao.AllocationDAO;
import org.ourgrid.peer.dao.ConsumerDAO;
import org.ourgrid.peer.dao.DiscoveryServiceClientDAO;
import org.ourgrid.peer.dao.LocalWorkersDAO;
import org.ourgrid.peer.dao.PeerCertificationDAO;
import org.ourgrid.peer.dao.PeerHistoryDAO;
import org.ourgrid.peer.dao.RequestDAO;
import org.ourgrid.peer.dao.UsersDAO;
import org.ourgrid.peer.dao.statistics.JobDAO;
import org.ourgrid.peer.dao.statistics.LoginDAO;
import org.ourgrid.peer.dao.statistics.PeerDAO;
import org.ourgrid.peer.dao.statistics.WorkerDAO;
import org.ourgrid.peer.dao.trust.TrustCommunitiesDAO;
import org.ourgrid.peer.status.PeerCompleteHistoryStatus;
import org.ourgrid.peer.status.PeerCompleteHistoryStatusBuilder;
import org.ourgrid.peer.status.PeerCompleteStatus;
import org.ourgrid.peer.to.AllocableWorker;
import org.ourgrid.peer.to.Consumer;
import org.ourgrid.peer.to.LocalAllocableWorker;
import org.ourgrid.peer.to.LocalConsumer;
import org.ourgrid.peer.to.LocalWorker;
import org.ourgrid.peer.to.PeerBalance;
import org.ourgrid.peer.to.PeerUser;
import org.ourgrid.peer.to.RemoteAllocableWorker;
import org.ourgrid.peer.to.RemoteConsumer;
import org.ourgrid.reqtrace.Req;
import org.ourgrid.worker.WorkerConstants;

import br.edu.ufcg.lsd.commune.Application;
import br.edu.ufcg.lsd.commune.ApplicationProperties;
import br.edu.ufcg.lsd.commune.CommuneRuntimeException;
import br.edu.ufcg.lsd.commune.api.FailureNotification;
import br.edu.ufcg.lsd.commune.api.MonitoredBy;
import br.edu.ufcg.lsd.commune.api.RecoveryNotification;
import br.edu.ufcg.lsd.commune.container.Container;
import br.edu.ufcg.lsd.commune.container.ContainerContext;
import br.edu.ufcg.lsd.commune.container.ObjectDeployment;
import br.edu.ufcg.lsd.commune.container.control.ApplicationControlClient;
import br.edu.ufcg.lsd.commune.container.control.ApplicationServerController;
import br.edu.ufcg.lsd.commune.container.control.ControlOperationResult;
import br.edu.ufcg.lsd.commune.container.logging.CommuneLogger;
import br.edu.ufcg.lsd.commune.container.servicemanager.dao.ContainerDAO;
import br.edu.ufcg.lsd.commune.container.servicemanager.dao.DAO;
import br.edu.ufcg.lsd.commune.identification.DeploymentID;
import br.edu.ufcg.lsd.commune.identification.ServiceID;
import br.edu.ufcg.lsd.commune.network.certification.CertificationUtils;
import br.edu.ufcg.lsd.commune.network.xmpp.XMPPProperties;

/**
 * Perform the Peer component control actions.
 */
@Req("REQ010")
public class PeerComponentController extends ApplicationServerController implements PeerManager {
	
	private static final String CONF_XML_PATH = "peer-hibernate.cfg.xml";
	

	@Override
	public String getComponentName() {
		return "Peer";
	}

	@Override
	protected void createDAOs() {
		createDAO(UsersDAO.class);
	    createDAO(ConsumerDAO.class);
		createDAO(LocalWorkersDAO.class);
		createDAO(AllocationDAO.class);
		createDAO(RequestDAO.class);
		createDAO(DiscoveryServiceClientDAO.class);
		createDAO(AccountingDAO.class);
		createDAO(TrustCommunitiesDAO.class);
		createDAO(PeerHistoryDAO.class);
		
		//Statistics
		createDAO(JobDAO.class);
		createDAO(LoginDAO.class);
		createDAO(PeerDAO.class);
		createDAO(UsersDAO.class);
		createDAO(WorkerDAO.class);

		//Certification
		createDAO(PeerCertificationDAO.class);
	}
	
	private void createDAO(Class<? extends DAO> clazz) {
		getServiceManager().createDAO(clazz);
	}
	
	@Override
	protected void createServices() {
		
		getServiceManager().deploy(PeerConstants.WORKER_MANAGEMENT_CLIENT_OBJECT_NAME, new WorkerManagementClientController());
		getServiceManager().deploy(PeerConstants.LOCAL_ACCESS_OBJECT_NAME, new LocalWorkerProviderController());
		getServiceManager().deploy(PeerConstants.REMOTE_ACCESS_OBJECT_NAME, new RemoteWorkerProviderController());
		getServiceManager().deploy(PeerConstants.REMOTE_WORKER_PROVIDER_CLIENT, new RemoteWorkerProviderClientController());
		getServiceManager().deploy(PeerConstants.REMOTE_WORKER_MANAGEMENT_CLIENT, new RemoteWorkerManagementClientController());
		getServiceManager().deploy(PeerConstants.ACCOUNTING_OBJECT_NAME, new AccountingController());
		getServiceManager().deploy(PeerConstants.WORKER_SPEC_LISTENER_OBJECT_NAME, new WorkerSpecListenerController());
		getServiceManager().deploy(PeerConstants.DS_CLIENT, new DiscoveryServiceClientController());
	
	}
	
	@Override
	protected void createMonitors() {
		
		getServiceManager().deploy(PeerConstants.WORKER_MONITOR_OBJECT_NAME, 
				new WorkerNotificationController());
		getServiceManager().deploy(PeerConstants.CLIENT_MONITOR_OBJECT_NAME, 
				new WorkerProviderClientFailureController());
		getServiceManager().deploy(PeerConstants.REMOTE_WORKER_PROVIDER_CLIENT_MONITOR, 
				new RemoteWorkerProviderClientFailureController());
		getServiceManager().deploy(PeerConstants.REMOTE_WORKER_MONITOR_OBJECT_NAME, 
				new RemoteWorkerFailureController());

	}

	@Override
	protected void startComponent() throws Exception {
		
		HibernateUtil.setUp(CONF_XML_PATH);
		
		//TODO Discovery Service Client
		configRepetitionActions();
		configBalanceRanking();
		
		if (shouldJoinCommunity()) {
			
			getServiceManager().deploy(PeerConstants.DS_MONITOR_OBJECT_NAME, 
					new DiscoveryServiceNotificationController());
			
			ContainerContext containerContext = getServiceManager().getContainerContext();
			String networkStr = containerContext.getProperty(PeerConfiguration.PROP_DS_NETWORK);
			
			if (networkStr == null || networkStr.equals("")) {
				throw new CommuneRuntimeException("Context could not be loaded. " +
						"If join community is set to 'yes', Discovery Service network proeprty is required.");
			}
			
			List<ServiceID> dsIds = PeerConfiguration.parseNetwork(getServiceManager());
			
			for (ServiceID dsId : dsIds) {
				getServiceManager().registerInterest(PeerConstants.DS_MONITOR_OBJECT_NAME, dsId.toString(), 
						DiscoveryService.class);
				getServiceManager().getDAO(DiscoveryServiceClientDAO.class).addDsID(dsId);
			}
			
			getServiceManager().addActionForRepetition(PeerConstants.DS_ACTION_NAME, 
					new DiscoveryServiceUpdateAction());
			
		}

		LocalWorkersDAO localWorkerDAO = getServiceManager().getDAO(LocalWorkersDAO.class);
		localWorkerDAO.setWorkerDAO(getServiceManager().getDAO(WorkerDAO.class));
		localWorkerDAO.setPeerDAO(getServiceManager().getDAO(PeerDAO.class));
		
		PeerControl.getInstance().updatePeer(getServiceManager());
		PeerControl.getInstance().registerPeerStatusChange(getServiceManager());
		LoginControl.getInstance().clearLogins(getServiceManager());
		
		getServiceManager().scheduleActionWithFixedDelay(PeerConstants.UPDATE_PEER_UPTIME_ACTION_NAME, 
				PeerConstants.UPDATE_UPTIME_DELAY, TimeUnit.SECONDS);
		
		registerInterestOnPersistedWorkers();
		
		CommuneLogger logger = getServiceManager().getLog();
		logger.info(PeerControlMessages.getSuccessfullyStartedPeerMessage());
	}

	private void registerInterestOnPersistedWorkers() {
		
		Collection<Worker> allActiveWorkers = WorkerControl.getInstance().findAllActiveWorkers(getServiceManager()); 
		
		for (Worker worker : allActiveWorkers) {

			ServiceID workerServiceID =  LocalWorkersDAO.parseServiceID(worker.getAddress());

			WorkerControl.getInstance().statusChanged(workerServiceID, 
					LocalWorkerState.CONTACTING, getServiceManager());
			
			getServiceManager().registerInterest(
					PeerConstants.WORKER_MONITOR_OBJECT_NAME, workerServiceID.toString(), 
					WorkerManagement.class);
		}
		
	}

	/**
	 * @param propJoinCommunity
	 * @return
	 */
	private boolean shouldJoinCommunity() {
		
		return getServiceManager().getContainerContext().isEnabled(PeerConfiguration.PROP_JOIN_COMMUNITY);
		
	}

	private void configRepetitionActions() {
		
		getServiceManager().addActionForRepetition(PeerConstants.REQUEST_WORKERS_ACTION_NAME, 
				new RequestWorkersAction());
		
		getServiceManager().addActionForRepetition(PeerConstants.SAVE_ACCOUNTING_ACTION_NAME, 
				new SaveAccountingAction());
		
		getServiceManager().addActionForRepetition(PeerConstants.UPDATE_PEER_UPTIME_ACTION_NAME, 
				new UpdatePeerUptimeAction());
	}

	private <T extends DAO> T getDAO(Class<T> class1) {
		return getServiceManager().getDAO(class1);
	}

	@Override
	public void stop(boolean callExit, boolean force, 
			@MonitoredBy(Application.CONTROL_OBJECT_NAME) ApplicationControlClient client) {
		saveRanking();
		leaveCommunity();
		disconnectActiveWorkers();
		
		super.stop(callExit, force, client);
	}

	private void disconnectActiveWorkers() {
		
		if (canStatusBeUsed()) {
			Collection<ServiceID> localWorkersIDs = WorkerControl.getInstance().getLocalWorkersIDs(getServiceManager());
			
			for (ServiceID workerID : localWorkersIDs) {
				WorkerControl.getInstance().statusChanged(workerID, LocalWorkerState.CONTACTING, 
						getServiceManager());
			}
		}
	}

	private void leaveCommunity() {
		
		ContainerDAO dao = getServiceManager().getContainerDAO();
		if (dao.isStarted() && shouldJoinCommunity()) {
			DiscoveryService discoveryService = getServiceManager().getDAO(
					DiscoveryServiceClientDAO.class).getDiscoveryService();
			
			if (discoveryService != null) {
				DiscoveryServiceClient dsClient = (DiscoveryServiceClient) 
				getServiceManager().getObjectDeployment(PeerConstants.DS_CLIENT).getObject();
				discoveryService.leaveCommunity(dsClient);
			}
			
		}
	}

	private void saveRanking() {
		
		ObjectDeployment objectDeployment = 
			getServiceManager().getObjectDeployment(PeerConstants.ACCOUNTING_OBJECT_NAME);
		
		if (objectDeployment != null) {
			PeerAccounting accountingController = (PeerAccounting) objectDeployment.getObject();
			accountingController.saveRanking();
		}
	}
	
	@Override
	protected boolean validateStartSenderPublicKey(ApplicationControlClient client, String senderPublicKey) {
		
		if(!getServiceManager().isThisMyPublicKey(senderPublicKey)) {
			getServiceManager().getLog().warn(PeerControlMessages.getUnknownSenderStartingPeerMessage(senderPublicKey));
			return false;
		}
		return true;
	}

	@Override
	protected boolean validateStopSenderPublicKey(ApplicationControlClient client, String senderPublicKey) {
		
		if(!getServiceManager().isThisMyPublicKey(senderPublicKey)) {
			getServiceManager().getLog().warn(PeerControlMessages.getUnknownSenderStoppingPeerMessage(senderPublicKey));
			return false;
		}
		return true;
	}

	/**
	 * Load balances from the ranking file and configure the timer to save
	 * the balances in the file periodically.
	 */
	private void configBalanceRanking() {
		
		getDAO(AccountingDAO.class).loadBalancesRanking(
				getServiceManager().getContainerContext().getProperty(PeerConfiguration.PROP_RANKINGFILE));
		long frequence = AccountingConstants.RANKING_SAVING_FREQ;

		getServiceManager().scheduleActionWithFixedDelay(PeerConstants.SAVE_ACCOUNTING_ACTION_NAME, frequence, TimeUnit.SECONDS);
	}

	/**
	 * Set the local Workers specification for this Peer.
	 * 
	 * <ul>
	 *   <li> Check if the Peer can have his local worker set;
	 *   <li> Shutdown Peer modules;
	 *   <li> Discard old workers;
	 *   <li> Register new workers;
	 *   <li> Send operation result messsage to client.
	 * </ul>
	 * 
	 * @param controlClient Client that will receive the operation result message
	 * @param newWorkers Collection of local workers specification
	 */
	@Req({"REQ010", "REQ043"})
	public void setWorkers(
			@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerControlClient controlClient, 
			List<WorkerSpec> newWorkers) {
	
		ControlOperationResult result;
		String senderPubKey = getServiceManager().getSenderPublicKey();
		
		if(canComponentBeUsed(controlClient)) {
			
			if(!getServiceManager().isThisMyPublicKey(senderPubKey)) {
				getServiceManager().getLog().warn(PeerControlMessages.getUnknownSenderSettingWorkersMessage(senderPubKey));
				return;
			}
			
			discardOldWorkers(newWorkers);
			registerNewWorkers(newWorkers);
			result = new ControlOperationResult();
			controlClient.operationSucceed(result);
		}
	}
	
	/**
	 * Add a new user for this peer.
	 * 
	 * @param controlClient Client that will receive the operation result message
	 * @param login User login
	 * @param password user password
	 */
	public void addUser(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerControlClient peerControlClient, String login,
			String password) {

		ControlOperationResult controlOperationResult = new ControlOperationResult();
		try {
			
			UserControl.getInstance().addUser(login, password, 
					getServiceManager(), 
					getDAO(UsersDAO.class));
			
			controlOperationResult.setResult(login);
		} catch (Exception e) {
			controlOperationResult.setErrorCause(e);
		}
		
		peerControlClient.operationSucceed(controlOperationResult);
	}
	
	/**
	 * Remove a user for this peer.
	 * 
	 * @param controlClient Client that will receive the operation result message
	 * @param login User login
	 */
	public void removeUser(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerControlClient peerControlClient, String login) {

		ControlOperationResult controlOperationResult = new ControlOperationResult();
		try {
			
			UserControl.getInstance().removeUser(login, getDAO(UsersDAO.class));
			
			controlOperationResult.setResult(login);
		} catch (Exception e) {
			controlOperationResult.setErrorCause(e);
		}
		
		peerControlClient.operationSucceed(controlOperationResult);
	}

	private void discardOldWorkers(Collection<WorkerSpec> newWorkers) {
		
		Object[] currentWorkersIDs = WorkerControl.getInstance().getLocalWorkersIDs(getServiceManager()).toArray();

		for (int i = 0; i < currentWorkersIDs.length; i++) {
			
			ServiceID currentWorkerID = (ServiceID) currentWorkersIDs[i];
			
			if (!doesWorkerContinues(currentWorkerID, newWorkers)) {
				removeWorker(currentWorkerID);
			}	
		}
	}
	
	private boolean doesWorkerContinues(ServiceID oldWorkerID, Collection<WorkerSpec> newWorkers) {
		
		for (WorkerSpec newWorkerSpec : newWorkers) {
			if (newWorkerSpec.getServiceID().equals(oldWorkerID)) {
				return true;
			}
		}
		return false;
	}
	
	private void discardWorker(LocalWorker oldLocalWorker) {
		
		LocalAllocableWorker allocableWorkerToDeallocate = 
			getDAO(AllocationDAO.class).removeLocalAllocableWorker(
					oldLocalWorker.getPublicKey());

		if (allocableWorkerToDeallocate != null) {
			
			if ( (allocableWorkerToDeallocate.getStatus().isAllocated())) {
				oldLocalWorker.getWorkerManagement().stopWorking();
			}			

		}
	}

	private void registerNewWorkers(Collection<WorkerSpec> newWorkers) {
		
		for ( WorkerSpec newWorkerSpec : newWorkers ) {
			registerNewWorker(newWorkerSpec);
		}
		
	}

	private void registerNewWorker(WorkerSpec newWorkerSpec) {
		LocalWorkersDAO localWorkersDAO = getDAO(LocalWorkersDAO.class);
		
		ContainerContext containerContext = getServiceManager().getContainerContext();
		String peerUser = containerContext.getProperty(XMPPProperties.PROP_USERNAME);
		String peerServer = containerContext.getProperty(XMPPProperties.PROP_XMPP_SERVERNAME);
		
		newWorkerSpec.removeAttribute(WorkerSpec.ATT_PASSWORD);
		newWorkerSpec.putAttribute(WorkerSpec.ATT_PROVIDER_PEER, peerUser + "@" + peerServer);
		
		if (WorkerControl.getInstance().isNewWorker(newWorkerSpec.getServiceID(), localWorkersDAO)) {
			ServiceID workerID = newWorkerSpec.getServiceID();
			getServiceManager().registerInterest(
					PeerConstants.WORKER_MONITOR_OBJECT_NAME, workerID.toString(), 
					WorkerManagement.class);
			LocalWorker localWorker = new LocalWorker(newWorkerSpec, workerID);
			
			WorkerControl.getInstance().addLocalWorker(localWorker, getServiceManager());
			
		} else {
			LocalWorker localWorker = WorkerControl.getInstance().getLocalWorker(newWorkerSpec.getServiceID(), localWorkersDAO);
			localWorker.setWorkerSpec(newWorkerSpec);
			WorkerControl.getInstance().updateWorker(newWorkerSpec, getServiceManager());
			
			LocalAllocableWorker allocableWorker = 
				getServiceManager().getDAO(AllocationDAO.class).getLocalAllocableWorker(localWorker.getPublicKey());
			
			//If there was no request, no allocable worker was created
			if (allocableWorker != null) {
				allocableWorker.getLocalWorker().setWorkerSpec(newWorkerSpec);
			}
			
		}
	}

	/* Status */
	
	private static final WorkerInfoComparator WORKER_INFO_COMPARATOR = new WorkerInfoComparator();
	
	/**
	 * Retrieves to the callback client info on the this Peer's Local Workers
	 * @param client The client that requested info
	 * @param peerStatusProviderServiceID The entityID of this Peer's status provider
	 */
	@Req("REQ036")
	public void getLocalWorkersStatus(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client) {
		if (canStatusBeUsed()) {
			client.hereIsLocalWorkersStatus(getServiceManager().getMyDeploymentID().getServiceID(), 
					getLocalWorkersInfo());
		}
	}

	/**
	 * Retrieves to the callback client info on this Peer's Users
	 * @param client The client that requested info
	 * @param entityID The entityID of this Peer's status provider
	 */
	@Req("REQ106")
	public void getUsersStatus(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client) {
		if (canStatusBeUsed()) {
			client.hereIsUsersStatus(getServiceManager().getMyDeploymentID().getServiceID(), getUsersInfo());
		}
	}

	/**
	 * Retrieves to the callback client info on the this Peer's Remote Workers
	 * @param client The client that requested info
	 * @param pspServiceID The entityID of this Peer's status provider
	 */
	@Req("REQ037")
	public void getRemoteWorkersStatus(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client) {
		if (canStatusBeUsed()) {
			client.hereIsRemoteWorkersStatus(getServiceManager().getMyDeploymentID().getServiceID(), getRemoteWorkersInfo());
		}
		
	}

	
	/**
	 * Retrieves to the callback client complete info on this Peer
	 * @param client The client that requested info
	 * @param entityID The entityID of this Peer's status provider
	 */
	@Req("REQ038a")
	public void getCompleteStatus(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client) {
		if (canStatusBeUsed()) {
			client.hereIsCompleteStatus( getServiceManager().getMyDeploymentID().getServiceID(), getCompleteStatus() );
		}
	}
	

	public void getCompleteHistoryStatus(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client, long time) {
		if (canStatusBeUsed()) {
			
			if (time == 0L) {
				time = getFirstPeerSatusChangeTime();
			}
			
			long until = Math.min(time + PeerConstants.AGGREGATOR_DATA_INTERVAL, System.currentTimeMillis());
			
			client.hereIsCompleteHistoryStatus(getServiceManager().getMyDeploymentID().getServiceID(), 
				getCompleteHistoryStatus(time, until), 
				until);
		}
	}
	
	private long getFirstPeerSatusChangeTime() {
		return PeerControl.getInstance().getFirstDBUpdateDate(getServiceManager());
	}

	@Req({"REQ036", "REQ038a"})
	private List<WorkerInfo> getLocalWorkersInfo() {
		List<WorkerInfo> localWorkersInfos = new LinkedList<WorkerInfo>();

		localWorkersInfos.addAll( getLocalWorkersInfo( LocalWorkerState.IN_USE ) );
		localWorkersInfos.addAll( getLocalWorkersInfo( LocalWorkerState.DONATED ) );
		localWorkersInfos.addAll( getLocalWorkersInfo( LocalWorkerState.IDLE ) );
		localWorkersInfos.addAll( getLocalWorkersInfo( LocalWorkerState.OWNER ) );
		localWorkersInfos.addAll( getLocalWorkersInfo( LocalWorkerState.CONTACTING ) );

		return localWorkersInfos;
	}

	@Req({"REQ036", "REQ038a"})
	private List<WorkerInfo> getLocalWorkersInfo(LocalWorkerState status) {
		if(status.isAllocated()){
			return getAllocatedWorkersInfos(status);
		}
		return getUnallocatedWorkersInfos(status);
	}

	@Req({"REQ036", "REQ038a"})
	private List<WorkerInfo> getAllocatedWorkersInfos(LocalWorkerState status) {
		AllocationDAO dao = getServiceManager().getDAO(AllocationDAO.class);
		
		LocalAllocableWorker localAllocableWorker = null;
		Consumer consumer = null;
		DeploymentID deploymentID = null;
		ServiceID serviceID = null;
		
		List<WorkerInfo> result = new LinkedList<WorkerInfo>();
		for (LocalWorker localWorker : WorkerControl.getInstance().getLocalWorkers(status, getServiceManager())) {
			
			localAllocableWorker = dao.getLocalAllocableWorker(localWorker.getPublicKey());
			
			if (localAllocableWorker != null) {
				consumer = localAllocableWorker.getConsumer();
				
				if (consumer != null) {
					deploymentID = consumer.getDeploymentID();
					serviceID = deploymentID.getServiceID();
				}
			}
			
			result.add( new WorkerInfo( localWorker.getWorkerSpec(), status, serviceID) );
		}
		Collections.sort(result, WORKER_INFO_COMPARATOR);
		return result;
	}

	@Req({"REQ036", "REQ038a"})
	private List<WorkerInfo> getUnallocatedWorkersInfos(LocalWorkerState status) {
		List<WorkerInfo> result = new LinkedList<WorkerInfo>();
		for (LocalWorker localWorker : WorkerControl.getInstance().getLocalWorkers(status, getServiceManager())) {
				result.add( new WorkerInfo( localWorker.getWorkerSpec(), status, null) );
		}
		Collections.sort(result, WORKER_INFO_COMPARATOR);
		return result;
	}
	
	private static class WorkerInfoComparator implements Comparator<WorkerInfo> {
		public int compare(WorkerInfo o1, WorkerInfo o2) {
			String id1 = (o1.getId() == null) ? "" : o1.getId();
			String id2 = (o2.getId() == null) ? "" : o2.getId();
			return id1.compareTo(id2);
		}
	}
	
	@Req({"REQ022", "REQ038a", "REQ106"})
	private List<UserInfo> getUsersInfo() {
		
		List<UserInfo> consumingUsersResult = new LinkedList<UserInfo>();
		List<UserInfo> loggedUsersResult = new LinkedList<UserInfo>();
		List<UserInfo> offlineUsersResult = new LinkedList<UserInfo>();
		List<UserInfo> neverLoggedUsersResult = new LinkedList<UserInfo>();
		
		UsersDAO dao = getServiceManager().getDAO(UsersDAO.class);
		
		for (PeerUser user : UserControl.getInstance().getUsers(dao).values()) {
			
			//If the user uses authentication, and has no public key registered, 
			//then its status is NEVER_LOGGED
			if(user.getPublicKey() == null || user.getPublicKey().equals("")){
				neverLoggedUsersResult.add(new UserInfo(user.getUsername(), 
						user.getXMPPServer(), user.getPublicKey(), UserState.NEVER_LOGGED));
			}
			
			//If the user is not logged and does not use authentication or has a public key registered, 
			//then its status is OFFLINE
			if(user.getPublicKey() != null && !user.getPublicKey().equals("") && isOffline(user)){
				offlineUsersResult.add(new UserInfo(user.getUsername(), 
						user.getXMPPServer(), user.getPublicKey(), UserState.OFFLINE));
			}
			
			//If the user is online and consuming, its status is CONSUMING, otherwise its LOGGED
			if(dao.isLoggedUser(user.getPublicKey())){
				if(isUserConsuming(user)){
					consumingUsersResult.add(new UserInfo(user.getUsername(), user.getXMPPServer(), user.getPublicKey(), UserState.CONSUMING));
				} else {
					loggedUsersResult.add(new UserInfo(user.getUsername(), user.getXMPPServer(), user.getPublicKey(), UserState.LOGGED));
				}
			}
		}
		
		Collections.sort(consumingUsersResult);
		Collections.sort(loggedUsersResult);
		Collections.sort(offlineUsersResult);
		Collections.sort(neverLoggedUsersResult);
		
		List<UserInfo> completeOrderedUsersInfo = new LinkedList<UserInfo>();
		completeOrderedUsersInfo.addAll(consumingUsersResult);
		completeOrderedUsersInfo.addAll(loggedUsersResult);
		completeOrderedUsersInfo.addAll(offlineUsersResult);
		completeOrderedUsersInfo.addAll(neverLoggedUsersResult);
		
		return completeOrderedUsersInfo;
	}

	private boolean isOffline(PeerUser user) {
		return !(getServiceManager().getDAO(UsersDAO.class).isLoggedUser(user.getPublicKey()));
	}

	@Req({"REQ038a", "REQ106"})
	private boolean isUserConsuming(PeerUser user) {
		
		return getServiceManager().getDAO(ConsumerDAO.class).isUserConsuming(user.getPublicKey());
	}
	
	@Req("REQ038a")
	private List<LocalConsumerInfo> getLocalConsumersInfo() {
		
		ConsumerDAO consumerDAO = getServiceManager().getDAO(ConsumerDAO.class);
		List<LocalConsumer> localConsumers = consumerDAO.getLocalConsumers();
		
		List<LocalConsumerInfo> localConsumersInfo = new ArrayList<LocalConsumerInfo>();
		
		for (LocalConsumer localConsumer : localConsumers) {
			
			if (localConsumer.getDeploymentID() == null) {
				continue;
			}
			
			int noOfLocalWorkers = 0;
			int noOfRemoteWorkers = 0;
			
			for (AllocableWorker allWorker : localConsumer.getAllocableWorkers()) {
				if (allWorker.isWorkerLocal()) {
					noOfLocalWorkers++;
				} else {
					noOfRemoteWorkers++;
				}
			}
			
			LocalConsumerInfo consumerInfo = new LocalConsumerInfo(
					noOfLocalWorkers,
					localConsumer.getDeploymentID().getUserName() + '@' + localConsumer.getDeploymentID().getServerName(), 
					noOfRemoteWorkers);
			
			localConsumersInfo.add(consumerInfo);
		}
		
		return localConsumersInfo;
	}

	@Req("REQ038a")
	private List<ConsumerInfo> getRemoteConsumersInfo() {
		
		ConsumerDAO consumerDAO = getServiceManager().getDAO(ConsumerDAO.class);
		List<RemoteConsumer> remoteConsumers = consumerDAO.getRemoteConsumers();
		
		List<ConsumerInfo> remoteConsumersInfo = new ArrayList<ConsumerInfo>();
		
		for (RemoteConsumer remoteConsumer : remoteConsumers) {
			
			if (remoteConsumer.getDeploymentID() == null) {
				continue;
			}
			
			int noOfLocalWorkers = 0;
			
			for (AllocableWorker allWorker : remoteConsumer.getAllocableWorkers()) {
				if (allWorker.isWorkerLocal()) {
					noOfLocalWorkers++;
				} 
			}
			
			ConsumerInfo consumerInfo = new ConsumerInfo(
					noOfLocalWorkers,
					remoteConsumer.getDeploymentID().getUserName() + '@' + remoteConsumer.getDeploymentID().getServerName());
			
			remoteConsumersInfo.add(consumerInfo);
		}
		
		return remoteConsumersInfo;
	}

	@Req("REQ038a")
	private NetworkOfFavorsStatus getNetworkOfFavorsStatus() {
		// TODO Auto-generated method stub
		return new NetworkOfFavorsStatus(new HashMap<String, PeerBalance>());
	}

	/**
	 * @param client
	 * @param entityID
	 */
	@Req("REQ035")
	public void getNetworkOfFavorsStatus(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client) {
		
		if (canStatusBeUsed()) {
			AccountingDAO accountingDAO = getServiceManager().getDAO(AccountingDAO.class);
			PeerDAO peerDAO = getServiceManager().getDAO(PeerDAO.class);
			NetworkOfFavorsStatus nofStatus = new NetworkOfFavorsStatus(
					AccountingControl.getInstance().getBalances(
							CertificationUtils.getCertSubjectDN(getServiceManager().getMyCertPath()), 
							accountingDAO, peerDAO));
			
			client.hereIsNetworkOfFavorsStatus(getServiceManager().getMyDeploymentID().getServiceID(), nofStatus);
		}		
	}
	
	@Req({"REQ038a", "REQ037"})
	private List<RemoteWorkerInfo> getRemoteWorkersInfo() {
		List<RemoteWorkerInfo> remoteWorkersInfo = new LinkedList<RemoteWorkerInfo>();

		List<RemoteAllocableWorker> remoteAllocables = getServiceManager().getDAO(AllocationDAO.class).getRemoteAllocableWorkers();
		
		if (remoteAllocables != null) {
			for (RemoteAllocableWorker remoteAllocable : remoteAllocables) {
				
				Consumer consumer = remoteAllocable.getConsumer();

				ServiceID consumerID = null;
				if (consumer != null) {
					consumerID = consumer.getDeploymentID().getServiceID();
				}
				
				remoteWorkersInfo.add(
						new RemoteWorkerInfo(remoteAllocable.getWorkerSpec(), remoteAllocable.getProviderID(), consumerID));
			}
			Collections.sort(remoteWorkersInfo, WORKER_INFO_COMPARATOR);
		}
		
		return remoteWorkersInfo;
		
	}
	
	@Req("REQ038a")
	private PeerCompleteStatus getCompleteStatus() {
		PeerCompleteStatus completeStatus = new PeerCompleteStatus( 
				getLocalWorkersInfo(), 
				getRemoteWorkersInfo(),
				getLocalConsumersInfo(), 
				getRemoteConsumersInfo(), 
				getUsersInfo(), 
				getNetworkOfFavorsStatus(), 
				getCommunityInfo(), 
				getServiceManager().getContainerDAO().getUpTime(), 
				getServiceManager().getContainerContext().getProperty( PeerConfiguration.PROP_LABEL ),
				Configuration.VERSION.toString(), 
				getDescription(getServiceManager().getContainerContext(), 
						getServiceManager().getDAO(DiscoveryServiceClientDAO.class).isConnected()));
		
		return completeStatus;
	}
	
	private CommunityInfo getCommunityInfo() {
		
		String dsAddress = null;
		DiscoveryServiceClientDAO dsDao = getServiceManager().getDAO(DiscoveryServiceClientDAO.class);
		
		if (dsDao.isConnected()) {
			DeploymentID dsID = getServiceManager().getStubDeploymentID(dsDao.getDiscoveryService());
			dsAddress = dsID.getContainerID().getUserAtServer();
		}
		
		List<String> connectedPeers = new ArrayList<String>();
		for (DeploymentID peerId : dsDao.getProviders().keySet()) {
			connectedPeers.add(peerId.getUserName() + '@' + peerId.getServerName());
		}
		
		return new CommunityInfo(dsAddress, 
				dsDao.isConnected() ? DiscoveryServiceState.UP : DiscoveryServiceState.CONTACTING,
				connectedPeers);
	}

	private PeerCompleteHistoryStatus getCompleteHistoryStatus(long since, long until) {

		PeerCompleteHistoryStatusBuilder builder = new PeerCompleteHistoryStatusBuilder();
		PeerCompleteHistoryStatus completeStatus = builder.buildCompleteHistoryStatus(
				since, until, getServiceManager().getContainerDAO().getUpTime(), 
				getDescription(getServiceManager().getContainerContext(),
						getServiceManager().getDAO(DiscoveryServiceClientDAO.class).isConnected()));
		
		return completeStatus;
	}
	
	private String getDescription(ContainerContext containerContext, boolean isDsConnected) {
		
		StringBuilder conf = new StringBuilder();

		conf.append( "\tVersion: " ).append( Configuration.VERSION ).append( LINE_SEPARATOR );

		conf.append( "\tConfiguration directory: " );
		conf.append( containerContext.getProperty(ApplicationProperties.PROP_CONFDIR) );
		conf.append( LINE_SEPARATOR );
		
		conf.append( containerContext.toString() );

		conf.append( "\tLabel: " );
		conf.append( containerContext.getProperty( PeerConfiguration.PROP_LABEL ) );
		conf.append( LINE_SEPARATOR );

		conf.append( "\tJoin community: " );
		conf.append( containerContext.getProperty( PeerConfiguration.PROP_JOIN_COMMUNITY ) );
		conf.append( LINE_SEPARATOR );
		
		if (containerContext.isEnabled(PeerConfiguration.PROP_JOIN_COMMUNITY)) {
			conf.append( "\tDiscovery Service List: " );
			conf.append( LINE_SEPARATOR );
			
			DiscoveryServiceClientDAO dao = getServiceManager().getDAO(DiscoveryServiceClientDAO.class);
			for (ServiceID dsID : dao.getDsIDs()) {
				conf.append("\t\t" + dsID.getContainerID().getUserAtServer());
				conf.append( LINE_SEPARATOR );
			}
			
			if (isDsConnected) {
				DeploymentID dsID = getServiceManager().getStubDeploymentID(dao.getDiscoveryService());
				conf.append("\tConnected to: " + dsID.getContainerID().getUserAtServer());
				conf.append( LINE_SEPARATOR );
			}
			
		}
		
		return conf.toString();
	}

	@Req("REQ110")
	public void getTrustStatus(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client) {
		if (canStatusBeUsed()) {
			client.hereIsTrustStatus(getServiceManager().getMyDeploymentID().getServiceID(), getTrustInfo());
		}
	}

	@Req("REQ110")
	private List<TrustyCommunity> getTrustInfo() {
		return getServiceManager().getDAO(TrustCommunitiesDAO.class).getTrustyCommunities();
	}

	/**
	 * Retrieves to the callback client the complete status of remote consumers.
	 * That is, information about workers, consumers, users, etc.
	 * @param client The client that requested info
	 * @param entityID The entityID of this Peer's status provider
	 */
	@Req("REQ034")
	public void getRemoteConsumersStatus(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client) {
		if (canStatusBeUsed()) {
			client.hereIsRemoteConsumersStatus(getServiceManager().getMyDeploymentID().getServiceID(), getRemoteConsumersStatus());
		}
	}
	
	/**
	 * Returns a list containing all the remote consumers' status.
	 * @return a list containing all the remote consumers' status.
	 */
	@Req("REQ034")
	private ArrayList<ConsumerInfo> getRemoteConsumersStatus() {
		Map<DeploymentID,ConsumerInfo> consumersMap = new HashMap<DeploymentID,ConsumerInfo>();

		List<AllocableWorker> allocableWorkers = getServiceManager().getDAO(AllocationDAO.class).getLocalAllocableWorkers();
		
		for (AllocableWorker allocableWorker : allocableWorkers) {

			Consumer consumer = allocableWorker.getConsumer();
			if (consumer == null || consumer.isLocal()) {
				continue;
			}

			DeploymentID consumerID = consumer.getDeploymentID();
			if (consumerID == null) {
				continue;
			}
			
			ConsumerInfo consumerInfo = consumersMap.get(consumerID);
			
			if (consumerInfo == null) {
				consumerInfo = new ConsumerInfo(0, consumerID.toString());
				consumersMap.put(consumerID, consumerInfo);
			}
			
			consumerInfo.setNumberOfLocalWorkers(consumerInfo.getNumberOfLocalWorkers() + 1);
		}
		
		
		ArrayList<ConsumerInfo> result = new ArrayList<ConsumerInfo>(consumersMap.values());
		Collections.sort(result, new ConsumerInfoComparator());
		return result;
	}
	
	/**
	 * This class compares two consumers info through the method <code>compare</code> regarding each consumer's id.
	 */
	public class ConsumerInfoComparator implements Comparator<ConsumerInfo> {

		/**
		 * Compares two consumer infos through their ids.
		 * @param o1 first consumer info
		 * @param o2 second consumer info
		 * @return the first consumer info id less the second consumer info id
		 */
		public int compare(ConsumerInfo o1, ConsumerInfo o2) {
			return o1.getConsumerIdentification().compareTo(o2.getConsumerIdentification());
		}
		
	}

	public void getLocalConsumersStatus(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client) {
		if (canStatusBeUsed()) {
			client.hereIsLocalConsumersStatus(getServiceManager().getMyDeploymentID().getServiceID(), getLocalConsumersInfo());
		}
		
	}

	public void registerAsListener(@MonitoredBy(Application.CONTROL_OBJECT_NAME) PeerStatusProviderClient client) {
		// TODO Auto-generated method stub
		
	}
	
	@RecoveryNotification
	public void controlClientIsUp(PeerControlClient client) {
		
	}
	
	@FailureNotification
	public void controlClientIsDown(PeerControlClient client) {
		
	}
	
	@RecoveryNotification
	public void statusProviderClientIsUp(PeerStatusProviderClient statusProviderClient) {
		
	}
	
	@FailureNotification
	public void statusProviderClientIsDown(PeerStatusProviderClient statusProviderClient) {
		
	}

	public void addWorker(@MonitoredBy(Application.CONTROL_OBJECT_NAME)PeerControlClient peerControlClient,
			WorkerSpec workerSpec) {
		
		String senderPubKey = getServiceManager().getSenderPublicKey();
		
		if(canComponentBeUsed(peerControlClient)) {
			
			if(!getServiceManager().isThisMyPublicKey(senderPubKey)) {
				getServiceManager().getLog().warn(
						PeerControlMessages.getUnknownSenderSettingWorkersMessage(
						senderPubKey));
				return;
			}
			
			registerNewWorker(workerSpec);
			peerControlClient.operationSucceed(new ControlOperationResult());
		}
	}

	public void removeWorker(@MonitoredBy(Application.CONTROL_OBJECT_NAME)PeerControlClient peerControlClient,
			WorkerSpec workerSpec) {
		
		String senderPubKey = getServiceManager().getSenderPublicKey();
		
		if(canComponentBeUsed(peerControlClient)) {
			
			if(!getServiceManager().isThisMyPublicKey(senderPubKey)) {
				getServiceManager().getLog().warn(
						PeerControlMessages.getUnknownSenderSettingWorkersMessage(
						senderPubKey));
				return;
			}
			
			ControlOperationResult controlOperationResult = new ControlOperationResult();
			
			try {
				removeWorker(workerSpec.getServiceID());
			} catch (IllegalArgumentException e) {
				controlOperationResult.setErrorCause(e);
			}
			
			peerControlClient.operationSucceed(controlOperationResult);
		}
		
	}

	private void removeWorker(ServiceID workerID) {
		
		LocalWorker oldLocalWorker = WorkerControl.getInstance().removeLocalWorker(workerID, getServiceManager().getDAO(WorkerDAO.class));
		
		if (oldLocalWorker == null) {
			throw new IllegalArgumentException("Worker with service ID [" + workerID + "] does not" +
					" is not registered in the peer.");
		}
		
		if (oldLocalWorker.isUp()) {
			discardWorker(oldLocalWorker);
		} 

		//WorkerControl.getInstance().removeLocalWorker(oldLocalWorker, getServiceManager());
		
		WorkerManagement workerManagement = oldLocalWorker.getWorkerManagement();
		
		if (workerManagement == null) {
			ServiceID workerManagementServiceID = new ServiceID(workerID.getContainerID(), 
					WorkerConstants.LOCAL_WORKER_MANAGEMENT);
			Container appContainer = getServiceManager().getApplication().getContainer();
			
			workerManagement = 
				(WorkerManagement) appContainer.createStub(
					workerManagementServiceID, WorkerManagement.class);
		}
		
		getServiceManager().release(workerManagement);
		
	}
	
		/**
	 * Set the local Workers specification for this Peer.
	 * <ul>
	 * <li>Check if the Peer can have his local worker set;
	 * <li>Shutdown Peer modules;
	 * <li>Discard old workers;
	 * <li>Register new workers;
	 * <li>Send operation result message to client.
	 * </ul>
	 * 
	 * @param controlClient Client that will receive the operation result
	 *        message
	 * @param newWorkers Collection of local workers specification
	 */
	// TODO @Req({"REQXX", "REQYY"})
	public void addAnnotationsWorkers( @MonitoredBy( Application.CONTROL_OBJECT_NAME ) PeerControlClient controlClient,
										List< WorkerSpec > newWorkersAnnotations ) {

		ControlOperationResult result;
		String senderPubKey = getServiceManager().getSenderPublicKey();

		if ( canComponentBeUsed( controlClient ) ) {

			// TODO Not sure if we can use like this...
			// In this way, I would like to allow anyone to update the peer's
			// work.
			// if(!getServiceManager().isThisMyPublicKey(senderPubKey)) {
			// getServiceManager().getLog().warn(PeerControlMessages.getUnknownSenderSettingWorkersMessage(senderPubKey));
			// return;
			// }

			updateWorkersWithAnnotations( newWorkersAnnotations );
			result = new ControlOperationResult();
			controlClient.operationSucceed( result );
		}
	}


	private void updateWorkersWithAnnotations( Collection< WorkerSpec > workersWithTags ) {

		for ( WorkerSpec workerSpec : workersWithTags ) {
			updateWorkerWithAnnotations( workerSpec );
		}

	}


	private void updateWorkerWithAnnotations( WorkerSpec workerSpecWithNewAnnotations ) {
		LocalWorkersDAO localWorkersDAO = getDAO( LocalWorkersDAO.class );

		// TODO The name of these methods are bad. Actually, this method checks
		// if a worker does not exist. It should be changed to doesWorkerExist
		// and then every call for isNewWorker be replaced to !doesWorkExist
		if ( WorkerControl.getInstance().isNewWorker( workerSpecWithNewAnnotations.getServiceID(), localWorkersDAO ) ) {
			return;
			// TODO Should We send some warn to up methods?
		}

		LocalWorker localWorker = WorkerControl.getInstance()
												.getLocalWorker( workerSpecWithNewAnnotations.getServiceID(),
																	localWorkersDAO );
		WorkerSpec newWorkerSpec = localWorker.getWorkerSpec();

		newWorkerSpec.addAllAnnotations( workerSpecWithNewAnnotations.getAnnotations() );

		WorkerControl.getInstance().updateWorker( newWorkerSpec, getServiceManager() );

		LocalAllocableWorker allocableWorker = getServiceManager()
																	.getDAO( AllocationDAO.class )
																	.getLocalAllocableWorker(
																								localWorker
																											.getPublicKey() );

		// If there was no request, no allocable worker was created
		if ( allocableWorker != null ) {
			allocableWorker.getLocalWorker().setWorkerSpec( newWorkerSpec );
		}
		// TODO Should We avail the alocableWorker issues?
		
	}

}